<template>
  <!-- 支付宝小程序使用$u.getRect()获取组件的根元素尺寸，所以在外面套一个"壳" -->
  <view>
    <view class="u-index-bar">
      <slot />
      <view
        v-if="showSidebar"
        class="u-index-bar__sidebar"
        @touchstart.stop.prevent="onTouchMove"
        @touchmove.stop.prevent="onTouchMove"
        @touchend.stop.prevent="onTouchStop"
        @touchcancel.stop.prevent="onTouchStop"
      >
        <view
          v-for="(item, index) in indexList"
          :key="index"
          class="u-index-bar__index"
          :style="{ zIndex: zIndex + 1, color: activeAnchorIndex === index ? activeColor : '' }"
          :data-index="index"
        >
          {{ item }}
        </view>
      </view>
      <view
        class="u-indexed-list-alert"
        v-if="touchmove && indexList[touchmoveIndex]"
        :style="{
          zIndex: alertZIndex,
        }"
      >
        <text>{{ indexList[touchmoveIndex] }}</text>
      </view>
    </view>
  </view>
</template>

<script>
var indexList = function () {
  var indexList = []
  var charCodeOfA = 'A'.charCodeAt(0)
  for (var i = 0; i < 26; i++) {
    indexList.push(String.fromCharCode(charCodeOfA + i))
  }
  return indexList
}

/**
 * indexList 索引列表
 * @description 通过折叠面板收纳内容区域,搭配<u-index-anchor>使用
 * @tutorial https://www.uviewui.com/components/indexList.html#indexanchor-props
 * @property {Number String} scroll-top 当前滚动高度，自定义组件无法获得滚动条事件，所以依赖接入方传入
 * @property {Array} index-list 索引字符列表，数组（默认A-Z）
 * @property {Number String} z-index 锚点吸顶时的层级（默认965）
 * @property {Boolean} sticky 是否开启锚点自动吸顶（默认true）
 * @property {Number String} offset-top 锚点自动吸顶时与顶部的距离（默认0）
 * @property {String} highlight-color 锚点和右边索引字符高亮颜色（默认#248DFA）
 * @event {Function} select 选中右边索引字符时触发
 * @example <u-index-list :scrollTop="scrollTop"></u-index-list>
 */
export default {
  name: 'u-index-list',
  props: {
    sticky: {
      type: Boolean,
      default: true,
    },
    zIndex: {
      type: [Number, String],
      default: '',
    },
    scrollTop: {
      type: [Number, String],
      default: 0,
    },
    offsetTop: {
      type: [Number, String],
      default: 0,
    },
    indexList: {
      type: Array,
      default() {
        return indexList()
      },
    },
    activeColor: {
      type: String,
      default: '#248DFA',
    },
  },
  created() {
    // #ifdef H5
    this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 44
    // #endif
    // #ifndef H5
    this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 0
    // #endif
    // 只能在created生命周期定义children，如果在data定义，会因为循环引用而报错
    this.children = []
  },
  data() {
    return {
      activeAnchorIndex: 0,
      showSidebar: true,
      // children: [],
      touchmove: false,
      touchmoveIndex: 0,
    }
  },
  watch: {
    scrollTop() {
      this.updateData()
    },
  },
  computed: {
    // 弹出toast的z-index值
    alertZIndex() {
      return this.$u.zIndex.toast
    },
  },
  methods: {
    updateData() {
      this.timer && clearTimeout(this.timer)
      this.timer = setTimeout(() => {
        this.showSidebar = !!this.children.length
        this.setRect().then(() => {
          this.onScroll()
        })
      }, 0)
    },
    setRect() {
      return Promise.all([this.setAnchorsRect(), this.setListRect(), this.setSiderbarRect()])
    },
    setAnchorsRect() {
      return Promise.all(
        this.children.map((anchor, index) =>
          anchor.$uGetRect('.u-index-anchor-wrapper').then((rect) => {
            Object.assign(anchor, {
              height: rect.height,
              top: rect.top,
            })
          })
        )
      )
    },
    setListRect() {
      return this.$uGetRect('.u-index-bar').then((rect) => {
        Object.assign(this, {
          height: rect.height,
          top: rect.top + this.scrollTop,
        })
      })
    },
    setSiderbarRect() {
      return this.$uGetRect('.u-index-bar__sidebar').then((rect) => {
        this.sidebar = {
          height: rect.height,
          top: rect.top,
        }
      })
    },
    getActiveAnchorIndex() {
      const { children } = this
      const { sticky } = this
      for (let i = this.children.length - 1; i >= 0; i--) {
        const preAnchorHeight = i > 0 ? children[i - 1].height : 0
        const reachTop = sticky ? preAnchorHeight : 0
        if (reachTop >= children[i].top) {
          return i
        }
      }
      return -1
    },
    onScroll() {
      const { children = [] } = this
      if (!children.length) {
        return
      }
      const { sticky, stickyOffsetTop, zIndex, scrollTop, activeColor } = this
      const active = this.getActiveAnchorIndex()
      this.activeAnchorIndex = active
      if (sticky) {
        let isActiveAnchorSticky = false
        if (active !== -1) {
          isActiveAnchorSticky = children[active].top <= 0
        }
        children.forEach((item, index) => {
          if (index === active) {
            let wrapperStyle = ''
            let anchorStyle = {
              color: `${activeColor}`,
            }
            if (isActiveAnchorSticky) {
              wrapperStyle = {
                height: `${children[index].height}px`,
              }
              anchorStyle = {
                position: 'fixed',
                top: `${stickyOffsetTop}px`,
                zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`,
                color: `${activeColor}`,
              }
            }
            item.active = active
            item.wrapperStyle = wrapperStyle
            item.anchorStyle = anchorStyle
          } else if (index === active - 1) {
            const currentAnchor = children[index]
            const currentOffsetTop = currentAnchor.top
            const targetOffsetTop = index === children.length - 1 ? this.top : children[index + 1].top
            const parentOffsetHeight = targetOffsetTop - currentOffsetTop
            const translateY = parentOffsetHeight - currentAnchor.height
            const anchorStyle = {
              position: 'relative',
              transform: `translate3d(0, ${translateY}px, 0)`,
              zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`,
              color: `${activeColor}`,
            }
            item.active = active
            item.anchorStyle = anchorStyle
          } else {
            item.active = false
            item.anchorStyle = ''
            item.wrapperStyle = ''
          }
        })
      }
    },
    onTouchMove(event) {
      this.touchmove = true
      const sidebarLength = this.children.length
      const touch = event.touches[0]
      const itemHeight = this.sidebar.height / sidebarLength
      let clientY = 0
      clientY = touch.clientY
      let index = Math.floor((clientY - this.sidebar.top) / itemHeight)
      if (index < 0) {
        index = 0
      } else if (index > sidebarLength - 1) {
        index = sidebarLength - 1
      }
      this.touchmoveIndex = index
      this.scrollToAnchor(index)
    },
    onTouchStop() {
      this.touchmove = false
      this.scrollToAnchorIndex = null
    },
    scrollToAnchor(index) {
      if (this.scrollToAnchorIndex === index) {
        return
      }
      this.scrollToAnchorIndex = index
      const anchor = this.children.find((item) => item.index === this.indexList[index])
      if (anchor) {
        this.$emit('select', anchor.index)
        uni.pageScrollTo({
          duration: 0,
          scrollTop: anchor.top + this.scrollTop,
        })
      }
    },
  },
}
</script>

<style lang="scss" scoped>
@import '../../libs/css/style.components.scss';

.u-index-bar {
  position: relative;
}

.u-index-bar__sidebar {
  position: fixed;
  top: 50%;
  right: 0;
  @include vue-flex;
  flex-direction: column;
  text-align: center;
  transform: translateY(-50%);
  user-select: none;
  z-index: 99;
}

.u-index-bar__index {
  font-weight: 500;
  padding: 8rpx 18rpx;
  font-size: 22rpx;
  line-height: 1;
}

.u-indexed-list-alert {
  position: fixed;
  width: 120rpx;
  height: 120rpx;
  right: 90rpx;
  top: 50%;
  margin-top: -60rpx;
  border-radius: 24rpx;
  font-size: 50rpx;
  color: #fff;
  background-color: rgba(0, 0, 0, 0.65);
  @include vue-flex;
  justify-content: center;
  align-items: center;
  padding: 0;
  z-index: 9999999;
}

.u-indexed-list-alert text {
  line-height: 50rpx;
}
</style>
